/*
 * UdpRtpSocket.cpp
 *
 *  Created on: 2018年5月1日
 *      Author: zhengboyuan
 */

#include "UdpRtpSocket.h"
#include "RtpPort.h"
#include "RtpHeader.h"
#include "RtpConst.h"
#include "CLog.h"


namespace av
{

UdpRtpSocket::UdpRtpSocket():
		m_socket()
{
}

UdpRtpSocket::~UdpRtpSocket()
{
	close();
}

bool UdpRtpSocket::open(const char* ip, int port)
{
	if (!m_socket.open(SOCK_DGRAM))
	{
		return false;
	}

	m_socket.setReuse();

	m_socket.trySetSendBufferSize(SEND_BUFFER_SIZE, MIN_BUFFER_SIZE);
	m_socket.trySetRecvBufferSize(SEND_BUFFER_SIZE, MIN_BUFFER_SIZE);


	bool done = false;
	if (port == 0)
	{
		for (size_t i = 0; i < (RtpPort::count() + 1); ++ i)
		{
			port = RtpPort::make();

			comn::SockAddr addr(ip, port);
			if (0 == m_socket.bind(addr))
			{
				m_addr = addr;
				done = true;
				break;
			}
		}
	}
	else
	{
		comn::SockAddr addr(ip, port);
		if (0 == m_socket.bind(addr))
		{
			m_addr = addr;
			done = true;
		}
	}

	if (!done)
	{
		close();
		return false;
	}

	m_socketPair.open();

	return true;
}

void UdpRtpSocket::close()
{
	m_socket.close();
	m_socketPair.close();

    m_pktQueue.clear();
}

bool UdpRtpSocket::isOpen()
{
	return m_socket.isOpen();
}

NetAddress UdpRtpSocket::getLocalAddr()
{
	return NetAddress(m_addr.getIPAddr(), m_addr.getPort());
}

bool UdpRtpSocket::send(RtpPacket& packet, int ms)
{
	if (!m_socket.isOpen())
	{
		return false;
	}

	if (m_peerAddr.getPort() == 0)
	{
		return true;
	}

	m_sendBuffer.ensure(packet.size + sizeof(RTP_FIXED_HEADER));

	RTP_FIXED_HEADER* header = (RTP_FIXED_HEADER*)m_sendBuffer.data();

	header->version = 2;
	header->marker = packet.mark;
	header->timestamp = htonl(m_initTime + packet.ts);
	header->ssrc = htonl(m_ssrc);
	header->seq_no = htons(m_seq ++);
	header->payload = packet.pt;

	uint8_t* data = m_sendBuffer.data() + sizeof(RTP_FIXED_HEADER);
	memcpy(data, packet.data, packet.size);

	data = m_sendBuffer.data();
	int size = packet.size + sizeof(RTP_FIXED_HEADER);

	m_socket.sendTo((char*)data, size, 0, m_peerAddr);

    int delayMs = m_sendDelay.send(size);
    m_sendDelay.delay(delayMs);

	return true;
}

static uint8_t  getByteFiled(uint8_t value, int startBit, int nBits)
{
    uint8_t mask = (1 << nBits) - 1;
    return (value >> startBit)&mask;
}


bool UdpRtpSocket::recv(RtpPacketHandler handler, int ms)
{
    if (!m_socketPair.checkReadable(m_socket.getHandle(), ms))
	//if (!m_socket.checkReadable(ms))
	{
		return false;
	}

    av::MediaPacketPtr pkt(new av::MediaPacket(), ffmpeg::PacketDeleter());
    av_new_packet(pkt.get(), RtpConst::MTU_MAX_SIZE);
    

	comn::SockAddr addr;
	int size = m_socket.receiveFrom((char*)pkt->data, pkt->size, 0, addr);
	if (size <= 0)
	{
		return false;
	}

    RTP_FIXED_HEADER* header = (RTP_FIXED_HEADER*)pkt->data;
    uint8_t* data = pkt->data + sizeof(RTP_FIXED_HEADER);
    size -= sizeof(RTP_FIXED_HEADER);

    pkt->stream_index = header->payload;
    pkt->pts = htonl(header->timestamp);
    pkt->duration = htons(header->seq_no);
    pkt->flags = header->marker;

    if (header->extension)
    {
        RTP_EXT_HEADER* extHeader = (RTP_EXT_HEADER*)(pkt->data + sizeof(RTP_FIXED_HEADER));
        int extLength = htons(extHeader->length);

        int isFec = getByteFiled(data[4], 5, 3);

        data += 4 + extLength * 4;
        size -= 4 + extLength * 4;

        uint16_t seq = htons(header->seq_no);

        if (isFec)
        {
            //CLog::debug("UdpRtpSocket::recv . seq: %d, size: %d, isFec: %d\n", seq, size, isFec);
            return false;
        }

        if (size <= 0)
        {
            return false;
        }
    }

    pkt->data = data;
    pkt->size = size;

    //av::MediaPacketPtr outPkt = pkt;
    m_pktQueue.insert(pkt);

    av::MediaPacketPtr outPkt;
    if (m_pktQueue.size() >= (size_t)RtpConst::RTP_ORDER_SIZE)
    {
        m_pktQueue.pop(outPkt);
    }
    
    if (!outPkt)
    {
        return false;
    }

    RtpPacket rtpPacket = RtpPacket();
    rtpPacket.data = outPkt->data;
    rtpPacket.size = outPkt->size;
    rtpPacket.mark = outPkt->flags > 0;
    rtpPacket.seq = outPkt->duration;
    rtpPacket.pt = outPkt->stream_index;
    rtpPacket.ts = (uint32_t)outPkt->pts;

    handler(rtpPacket);

	return true;
}

bool UdpRtpSocket::isConnected()
{
	return isOpen();
}

void UdpRtpSocket::abort()
{
    m_socketPair.makeReadable();
}



} /* namespace av */
